package org.jugile.util;

/*

Copyright (C) 2007-2011 Jukka Rahkonen  email: jukka.rahkonen@iki.fi

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA

*/

import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Serializable;
import java.io.Writer;
import java.util.ArrayList;
import java.util.List;

/**
 * About the Absolute Truth:
 * 
 * "In the beginning was the Word, and the Word was with God, and the Word was God." (John 1:1)
 * "And the Word was made flesh, and dwelt among us, (and we beheld his glory, the glory 
 *  as of the only begotten of the Father,) full of grace and truth." (John 1:14)
 * "And ye shall know the truth, and the truth shall make you free." (John 8:32)
 * "Jesus saith unto him, I am the way, the truth, and the life: 
 *  no man cometh unto the Father, but by me." (John 14:6)
 * "Sanctify them through thy truth: thy word is truth." (John 17:17)
 *  
 * ==========
 *  
 * Blob is easy to use file handling utility. It is for any binary large object and for
 * easy line reading from text files. Blob also handles mime multipart format.
 * 
 * @author jukka.rahkonen@iki.fi
 */
public class Blob extends Jugile implements Serializable {
	private static final long serialVersionUID = 1L;
	
	private byte[] data;
	public byte[] getData() { return data; }
	public void setData(byte[] v) { data = v; }
	
	private String type;
	public String getType() { return type; }
	public void setType(String v) { type = v; }
	
	private String filename;
	public String getFilename() { return filename; }
	public void setFilename(String v) { filename = v; }
	public boolean isFile() { return filename != null; }
	
	private String cid = null;
	public String getCid() { return cid; }
	public void setCid(String v) { cid = v; }
//	private boolean inline = false;
//	public boolean isInline() { return inline; }
//	public void setInline() { inline = true; }
	
	private String mimeName;
	/**
	 * Name of the mime content-disposition
	 * @return
	 */
	public String getMimeName() { return mimeName; }
	
	private BufferedInputStream bis;
	//private InputStream inputStream;
	private int buffersize = 512;
	private byte[] buffer;
	private int bufidx = 0;
	
	public Blob(String filename) {
		read(getResource(filename));
		this.filename = filename;
	}
	public Blob(File f) {
		String fname = "";
		try { fname = f.getCanonicalPath();	} catch(Exception e) { fail(e); }
		read(getResource(fname));
	}
	public Blob(InputStream is) {
		read(is);
	}
	public Blob(byte[] data) {
		this.data = data.clone();
	}
	public Blob() {
		data = null;
	}
	public Blob(BufferedInputStream bis) {
		this.bis = bis;
	}
	
	public Blob(InputStream is, int bufsize) {
		bis = new BufferedInputStream(is);
		buffersize = bufsize;
		buffer = new byte[buffersize];
		bufidx = 0;
	}
	
	public String readLine() { return getUtf8(readLineData()); }
	public byte[] readLineData() {
		if (bis == null) fail("no input stream");
		int c = -1;
		resetBuffer();
		try {
			while((c = bis.read()) != -1) {
				bufappend((byte)c);
				if (c == '\n') {
					byte[] res = new byte[bufidx];
					System.arraycopy(buffer, 0, res, 0, bufidx);
					return res;
				}
				//print("char: " + Character.valueOf((char)c));
			}
		} catch (Exception e) { fail(e); }
		return null;
	}

	public String readString(int bytes) { return getUtf8(readBytes(bytes)); }
	public byte[] readBytes(int bytes) {
		if (bis == null) fail("no input stream");
		int c = -1;
		resetBuffer();
		try {
			for (int i = 0; i < bytes; i++) {
				c = bis.read();
				if (c == -1) break;
				bufappend((byte)c);
			}
			byte[] res = new byte[bufidx];
			System.arraycopy(buffer, 0, res, 0, bufidx);
			return res;
		} catch (Exception e) { fail(e); }
		return null;		
	}
	
	private void bufappend(byte b) {
		buffer[bufidx++] = b;
		//print("append ("+bufidx+"): " + b + " " + (char)b);
		if (bufidx > buffersize-1) {
			// double the buffer
			byte[] buf2 = new byte[buffersize * 2];
			System.arraycopy(buffer, 0, buf2, 0, buffersize);
			buffersize = buffersize * 2;
			buffer = buf2;
		}
	}
	
	private void resetBuffer() {
		buffer = new byte[buffersize];
		bufidx = 0;
	}
	
	public void read(InputStream is) {
		data = null;
		try {
			int bufsize = is.available();
			//print("available: " + bufsize);
			if (bufsize == 0) bufsize = 100000;
			byte[] buf = new byte[bufsize];
			int numRead = 0;
			while ((numRead = is.read(buf)) >= 0) {
				append(buf, numRead);
				//print("read: " + numRead);
			}
			is.close();
		} catch (Exception e) { fail(e); }
	}
	
	enum State {
		START,
		HEADERS,
		BINARY,
		DONE
	};
	
	public static List<Blob> parseMime(InputStream is, String length, String boundary) {
		Blob b = new Blob(is, 10000);
		List<Blob> res = new ArrayList<Blob>();
		Blob current = null;
		State state = State.START;
		while (state != State.DONE) {
			byte[] buf = b.readLineData();
			if (buf.length > boundary.length() && buf.length < boundary.length() + 10) {
				String line = getUtf8(buf);
				if (line.contains(boundary)) {
					if (line.trim().endsWith("--")) {
						state = State.DONE;
						// save current
						if (current != null) res.add(current);
						break;
					}
					print("--- boundary ---");
					// start new Blob
					if (current != null) res.add(current);
					current = new Blob();
					state = State.HEADERS;
					continue;
				}
			}
			if (state == State.HEADERS) {
				String line = getUtf8(buf);
				print("header ("+line.length()+"): "+ line);
				if (line.equals("\r\n")) {
					state = State.BINARY;
					print(" state = binary");
					continue;
				}
				// parse header
				int idx = line.indexOf(':');
				String key = line.substring(0, idx).trim();
				String value = line.substring(idx+1).trim();
				//print(key + "=" + value);
				if (key.equals("Content-Disposition")) {
					String p[] = value.split(";");
					if ("form-data".equals(p[0].trim())) {
						for (int i = 1; i < p.length; i++) {
							String expr = p[i].trim();
							int idx2 = expr.indexOf('=');
							String var = expr.substring(0, idx2).trim();
							String val = expr.substring(idx2+1).trim();
							val = val.substring(1,val.length()-1); // remove quotes
							print("  " + var + "=" + val);
							if ("name".equals(var)) {
								current.mimeName = val;
							}
							if ("filename".equals(var)) {
								current.filename = val;
							}
						}
					}
				}
				if (key.equals("Content-Type")) {
					current.type = value;					
				}
				// ignore other headers for now
			}
			if (state == State.BINARY) {
				current.append(buf);
			}
		}		
		return res;
	}

	public static List<Blob> parseMimeBuffered(BufferedInputStream bis, String length, String boundary) {
		Blob b = new Blob(bis);
		List<Blob> res = new ArrayList<Blob>();
		Blob current = null;
		State state = State.START;
		while (state != State.DONE) {
			byte[] buf = b.readLineData();
			if (buf.length > boundary.length() && buf.length < boundary.length() + 10) {
				String line = getUtf8(buf);
				if (line.contains(boundary)) {
					if (line.trim().endsWith("--")) {
						state = State.DONE;
						// save current
						if (current != null) res.add(current);
						break;
					}
					//print("--- boundary ---");
					// start new Blob
					if (current != null) res.add(current);
					current = new Blob();
					state = State.HEADERS;
					continue;
				}
			}
			if (state == State.HEADERS) {
				String line = getUtf8(buf);
				//print("header ("+line.length()+"): "+ line);
				if (line.equals("\r\n")) {
					state = State.BINARY;
					//print(" state = binary");
					continue;
				}
				// parse header
				int idx = line.indexOf(':');
				String key = line.substring(0, idx).trim();
				String value = line.substring(idx+1).trim();
				//print(key + "=" + value);
				if (key.equals("Content-Disposition")) {
					String p[] = value.split(";");
					if ("form-data".equals(p[0].trim())) {
						for (int i = 1; i < p.length; i++) {
							String expr = p[i].trim();
							int idx2 = expr.indexOf('=');
							String var = expr.substring(0, idx2).trim();
							String val = expr.substring(idx2+1).trim();
							val = val.substring(1,val.length()-1); // remove quotes
							//print("  " + var + "=" + val);
							if ("name".equals(var)) {
								current.mimeName = val;
							}
							if ("filename".equals(var)) {
								current.filename = val;
							}
						}
					}
				}
				if (key.equals("Content-Type")) {
					current.type = value;					
				}
				// ignore other headers for now
			}
			if (state == State.BINARY) {
				current.append(buf);
			}
		}		
		return res;
	}
	
	
	
	public void append(byte b) {
		byte arr[]= {b};
		this.append(arr);
	}
	
	public void append(String str) {
		ByteArrayOutputStream bos = new ByteArrayOutputStream(16000);
		try {
			// UTF-8 appender
			OutputStreamWriter osw = new OutputStreamWriter(bos, "UTF-8");
			osw.append(str);
			osw.close();
		} catch (Exception e ) { fail(e); }
		append(bos.toByteArray(), bos.size());
	}
	public void append(Blob b) {
		append(b.data, b.data.length);
	}
	
	protected void append(byte[] buf) { append(buf, buf.length); }
	protected void append(byte[] buf, int count) {
		// add size with count
		int len = 0;
		if (data == null) {
			data = new byte[count];
		} else {
			len = data.length;
			byte[] data2 = new byte[len + count];
			System.arraycopy(data, 0, data2, 0, len);
			data = data2;
		}
		System.arraycopy(buf, 0, data, len, count);
	}
	
	
	public void write(String filename) {
		write(new File(filename));
	}
	public void write(File file) {
		if (data == null) fail("tried to write null data");
		try {
			FileOutputStream fos = new FileOutputStream(file);
			Writer out = new OutputStreamWriter(fos, "UTF8");
			out.write(toString());
			out.close();
		} catch (Exception e) { fail(e); }
	}
	public void writeBytes(String filename) {
		if (data == null) fail("tried to write null data");
		try {
			FileOutputStream fos = new FileOutputStream(new File(filename));
			fos.write(data);
			fos.close();
		} catch (Exception e) { fail(e); }
	}
	
	public int getSize() {
		if (data == null) return 0;
		return data.length;
	}
	
	public String[] getLinesRaw() {
		String str = new String(data);
		if (str == null) return new String[] {};
		return str.split("\r\n|\r|\n");
	}
	
	public String toStringRaw() {
		return new String(data);
	}
	
	public String toString() {
		return getUtf8(data);
	}
	
	public String[] getLines() {
		String str = toString();
		if (empty(str)) return new String[] {};
		return str.split("\r\n|\r|\n");
	}
	
}
